package javablackbelt.beans.factory.config;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ReflectionUtils;

public class Base64PropertyDecoderSupport implements Serializable {

	protected final Log logger = LogFactory.getLog(getClass());

	private Class<? extends Annotation> encodedPropAnnotationType;
	
	private transient final Map<Class<?>, List<Field>> encodedFieldsCache =
		new ConcurrentHashMap<Class<?>, List<Field>>();

	
	/**
	 * Specify the Encoded annotation to check for indicating Base 64 encoding.
	 */
	public void setEncodedPropAnnotationType(Class<? extends Annotation> encodedPropAnnotationType) {
		this.encodedPropAnnotationType = encodedPropAnnotationType;
	}
	
	public List<Field> findEncodedFields(Class<? extends Object> clazz) {
		if (this.encodedFieldsCache == null) {
			// Happens after deserialization, during destruction...
			return buildEncodedFieldsData(clazz);
		}
		// Quick check on the concurrent map first, with minimal locking.
		List<Field> fields = this.encodedFieldsCache.get(clazz);
		if (fields == null) {
			synchronized (this.encodedFieldsCache) {
				fields = this.encodedFieldsCache.get(clazz);
				if (fields == null) {
					fields = buildEncodedFieldsData(clazz);
					this.encodedFieldsCache.put(clazz, fields);
				}
				return fields;
			}
		}
		return fields;
	}

	public List<Field> buildEncodedFieldsData(final Class<? extends Object> clazz) {
		final List<Field> newFields = new ArrayList<Field>();
		final boolean debug = logger.isDebugEnabled();
		ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
			public void doWith(Field field) {
				if (encodedPropAnnotationType != null) {
					if (field.getAnnotation(encodedPropAnnotationType) != null) {
						newFields.add(field);
						if (debug) {
							logger.debug("Found base64 encoded property on class [" + clazz.getName() + "]: " + field);
						}
					}
				}
			}
		});
		return newFields;
	}
	
}
